import re
import binascii
from midi.exp import *
from midi.track import *
from midi.midi_event import *
from midi.midi_message import *
from midi.serializable import Serializable

class Sequence(Serializable):
    MTHD_ID_CODE = "4d546864" # MThd 识别码

    __file_format: str # MIDI文件格式
    __track_count: str # 音轨数
    __time_type: str # 时间类型
    __ext: str # 剩余 MThd 数据

    __tracks: list[Track] # 音轨列表

    def __init__(self, file_format: str, track_count: str, time_type: str, ext: str) -> None:
        self.__file_format = file_format
        self.__track_count = track_count
        self.__time_type = time_type
        self.__ext = ext
        self.__tracks = list()

    def getFileFormat(self) -> str:
        "4位十六进制字符串"
        return self.__file_format
    
    def setFileFormat(self, new_formats: int) -> None:
        if int(self.getTrackCount(), 16) > 1 and new_formats == 0:
            raise ValueError("The current file has more than 1 tracks, so MIDI file format 0 cannot be used.")
        self.__file_format = hex(new_formats)[2:]
        self.__file_format = "0"*(4-len(self.__file_format))+self.__file_format

    def getTrackCount(self) -> str:
        "4位十六进制字符串"
        return self.__track_count
    
    def __setTrackCount(self, new_track_count):
        self.__track_count = hex(new_track_count)[2:]
        self.__track_count = "0"*(4-len(self.__track_count))+self.__track_count

    def getTimeType(self) -> str:
        "4位十六进制字符串"
        return self.__time_type
    
    def getExt(self) -> str:
        return self.__ext

    def getTracks(self) -> list[Track]:
        return self.__tracks
    
    def mergeTrack(self, index: int, track: Track):
        '''
        将track音轨中的事件，合并入本序列中的指定音轨中。

        Parameters
        ----------
        index : int
            本序列中的音轨下标
        tract : Track
            新音轨（将新音轨中，除了最后一个事件，即音轨结束事件，之外的所有事件加入本序列中的指定音轨中）
        '''
        for event in track.getEvents()[0:len(track.getEvents())-1]:
            self.__tracks[index].appendEvent(event)
        self.__tracks[index].sortEvents()
    
    def insertTrack(self, index: int, track: Track):
        '''
        向本序列中的指定位置添加一条音轨。

        Notes
        -----
        若添加一条音轨之后，本序列中的音轨总数量>1，且文件格式为0，本方法不会自动更改文件格式。
        '''
        self.__tracks.insert(index, track)

    def appendTrack(self, track: Track):
        '''
        向本序列中添加一条音轨。

        Notes
        -----
        若添加一条音轨之后，本序列中的音轨总数量>1，且文件格式为0，本方法不会自动更改文件格式。
        '''
        self.__tracks.append(track)
    
    def deleteTrack(self, index: int):
        '''
        Parameters
        ----------
        index : int
            要删除第几条音轨，从0开始计数
        '''
        if int(self.getTrackCount(), 16) != 0 and index >= 0 and index < len(self.__tracks):
            del self.__tracks[index]
            self.__setTrackCount(int(self.getTrackCount(), 16)-1)

    def hex(self) -> str:
        s_mthd_data = self.getFileFormat()+self.getTrackCount()+self.getTimeType()+self.getExt()
        mthd_data_length = hex(len(s_mthd_data)//2)[2:]
        s_mthd_data_length = "0"*(8-len(mthd_data_length))+mthd_data_length
        s_data = ""
        for track in self.getTracks():
            s_data += track.hex()
        return Sequence.MTHD_ID_CODE+s_mthd_data_length+s_mthd_data+s_data

def read(file_name: str) -> Sequence:
    '''
    Parameters
    ----------
    file_name : str
        midi文件路径

    Returns
    -------
    out : Sequence
        Sequence对象
    '''

    with open(file_name, "rb") as file:
        b_data = file.read() # 获取二进制文件内容
    
    try:
        s_data = b_data.hex() # 转为十六进制内容
        ############################################## MThd ##############################################
        mthd_data = re.match('4d546864\w{20,}?', s_data, re.I) # 匹配 MThd 结构
        if mthd_data == None: # 验证
            raise ValueError("no MThd structure found")
        mthd_data = mthd_data[0][8:]

        # 读入 MThd 数据，跳过 MThd 数据总字节数解析
        mthd_file_format = mthd_data[8:12]
        mthd_track_count = mthd_data[12:16]
        mthd_time_type = mthd_data[16:20]
        ext_index = mthd_data.find("4d54726b")
        if ext_index != -1:
            mthd_ext = mthd_data[20:ext_index]
        else:
            mthd_ext = ""

        # 验证
        if int(mthd_file_format, 16) != 0 and int(mthd_file_format, 16) != 1: # 仅支持MIDI格式 0 和 1
            raise UnsupportedException("Invalid or unsupported file type: ", int(mthd_file_format, 16))
        if int(mthd_track_count, 16) > 1 and int(mthd_file_format, 16) == 0: # 验证MIDI文件格式和音轨数量是否匹配
            raise ValueError()

        seq = Sequence(mthd_file_format, mthd_track_count, mthd_time_type, mthd_ext)
        ############################################## Track ##############################################
        s_tracks: list[str] = re.findall('4d54726b.*?ff2f00', s_data, re.I) # 匹配 Track
        if len(s_tracks) != int(mthd_track_count, 16): # 验证 MThd 数据与实际音轨数量是否匹配
            raise ValueError("The number of tracks in MThd does not match the actual number of tracks")
        for s_track in s_tracks: # 遍历音轨
            track = Track()
            seq.appendTrack(track)
            index = 16 # 跳过音轨数据总字节数解析
            datum_time = 0 # 音轨基准时间
        ############################################## Event ##############################################
            while index < len(s_track):
        ############################################## Tick ##############################################
                bin_tick_list = list()
                for i in range(0, 4):
                    bin_tick = bin(int(s_track[index:index+2], 16))[2:]
                    bin_tick = "0"*(8-len(bin_tick))+bin_tick
                    bin_tick_list.append(bin_tick)
                    index = index+2
                    if bin_tick[0] == "0":
                        break
                tick = ""
                for bin_tick in bin_tick_list:
                    tick += bin_tick[1:]
                datum_time += int(tick, 2)
        ############################################## Message ##############################################
                message_status = s_track[index:index+2]
                index = index+2
                if message_status == MetaMessage.META_MESSAGE_ID_CODE: # 元消息
                    meta_type = s_track[index:index+2]
                    meta_data_length = s_track[index+2:index+4]
                    message = MetaMessage(message_status, meta_type, meta_data_length, s_track[index+4:index+4+int(meta_data_length, 16)*2])
                    index = index+4+int(meta_data_length, 16)*2
                elif message_status == SysexMessage.SYSEX_MESSAGE_BEGIN_ID_CODE: # 系统独占消息
                    data = s_track[index:s_track.index(SysexMessage.SYSEX_MESSAGE_END_ID_CODE, index)]
                    message = SysexMessage(message_status, data)
                    index = index+len(data)+2
                elif message_status[0] == ShortMessage.INSTRUMENT_ID_CODE: # 单附加数据短消息
                    message = ShortMessage(message_status, s_track[index:index+2], "00")
                    index = index+2
                else: # 双附加数据短消息
                    message = ShortMessage(message_status, s_track[index:index+2], s_track[index+2:index+4])
                    index = index+4
                track.appendEvent(MidiEvent(datum_time, message))
                
        return seq
    except UnsupportedException as e: raise e
    except BaseException as e: raise UnsupportedException(e, "not a valid MIDI file")